package http

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/thinkeridea/go-extend/exnet"
)

func GetRequestIP(r *http.Request) string {
	ip := exnet.ClientPublicIP(r)
	if ip == "" {
		ip = exnet.ClientIP(r)
	}
	return ip
}

func DoHttpTask(
	method string,
	url string,
	body interface{},
	timeout time.Duration,
	onPrepare func(*http.Request) (*http.Request, error),
	onCompleted func(request *http.Request, response *http.Response, client *http.Client) (err error),
) (err error) {

	if method == "" {
		err = fmt.Errorf("method must not empty")
		return
	}

	if url == "" {
		err = fmt.Errorf("url must not empty")
		return
	}

	if timeout <= 0 {
		err = fmt.Errorf("timeout must > 0")
		return
	}

	var request *http.Request
	var e error
	if body == nil {
		request, e = http.NewRequest(method, url, nil)
	} else {
		request, e = http.NewRequest(method, url, body.(io.Reader))
	}
	if e != nil {
		err = fmt.Errorf("http.NewRequest fail, %w", e)
		return
	}

	if onPrepare != nil {
		request, e = onPrepare(request)
		if e != nil {
			err = fmt.Errorf("onPrepare fail, %w", e)
			return
		}
		if request == nil {
			err = fmt.Errorf("request is nil after onPrepare")
			return
		}
	}

	client := &http.Client{Timeout: timeout}

	response, e := client.Do(request)
	if e != nil {
		err = fmt.Errorf("do request fail, %s", e)
		return
	}

	if onCompleted != nil {
		err = onCompleted(request, response, client)
	}

	_ = response.Body.Close()

	return
}

func DoJsonHttpTask(
	method string,
	url string,
	timeout time.Duration,
	token string,
	send interface{},
	recv interface{},
) (err error) {
	var sendBody interface{}

	if send != nil {
		sendJson, e := json.Marshal(send)
		if e != nil {
			err = fmt.Errorf("marshal send fail, %w", e)
			return
		}
		sendBody = bytes.NewReader(sendJson)
	}

	err = DoHttpTask(method, url, sendBody, timeout, func(request *http.Request) (*http.Request, error) {
		if token != "" {
			request.Header.Add("Authorization", token)
		}
		if send != nil {
			request.Header.Set("Content-Type", "application/json")
		}
		return request, nil
	}, func(request *http.Request, response *http.Response, client *http.Client) (err error) {
		if recv == nil {
			return
		}

		recvBody, e := ioutil.ReadAll(response.Body)
		if e != nil {
			err = fmt.Errorf("get data from res.Body fail")
			return
		}

		e = json.Unmarshal(recvBody, recv)
		if e != nil {
			err = fmt.Errorf("unmarshal send[%T] from [%s] fail, %s", recv, recvBody, err)
			return
		}
		return
	})

	return
}

func GetJson(
	url string,
	timeout time.Duration,
	token string,
	send interface{},
	recv interface{},
) error {
	return DoJsonHttpTask("GET", url, timeout, token, send, recv)
}

func PostJson(
	url string,
	timeout time.Duration,
	token string,
	send interface{},
	recv interface{},
) error {
	return DoJsonHttpTask("POST", url, timeout, token, send, recv)
}
